﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics;

namespace SimpleIR
{
	/// <summary>
	/// This class is used to represent all bytecode operations that take a scalar value on the eval stack and evaluate to a new value on the eval stack.
	/// </summary>
	public sealed class IRPointerIndexOp : IRByteCode
	{
		/// <summary>
		/// An array containing all the acceptable opcodes for this operation class (in order)
		/// </summary>
		public static readonly OpCode.Op[] AcceptableOps = { OpCode.Op.LoadLocalAddr, OpCode.Op.LoadGlobalAddr, OpCode.Op.IndexRefAddrByField, OpCode.Op.IndexRefAddrByArrayIndex };

		/// <summary>
		/// If this is a load local address this is the name of the local var.
		/// </summary>
		private readonly IRVarName m_var;

		/// <summary>
		/// If this is a load global address this is the name of the static field.
		/// </summary>
		private readonly IRStaticField m_sfld;

		/// <summary>
		/// If this is a index by member field operation this is the field to index to.
		/// </summary>
		private readonly IRMemberField m_fieldindex;

		/// <summary>
		/// If this is a index by member field operation this is the offset in the stack where the base reference/pointer is stored.
		/// </summary>
		private readonly int m_stackposition;

		/// <summary>
		/// If this is a index by array offset this is the type of the container to index on.
		/// </summary>
		private readonly IRArrayType m_basearraytype;

		private IRPointerIndexOp(OpCode.Op opc, IRVarName var, IRStaticField sfld, IRMemberField mfld, int stackpos, IRArrayType aty, IRSourceLocation sloc)
			: base(opc, sloc)
		{
			Debug.Assert(AcceptableOps.Contains(opc));

			this.m_var = var;
			this.m_sfld = sfld;
			this.m_fieldindex = mfld;
			this.m_stackposition = stackpos;
			this.m_basearraytype = aty;
		}

		/// <summary>
		/// Make the bytecode for loading the address of the given local variable.
		/// </summary>
		public static IRPointerIndexOp MakeLoadLocalAddr(IRVarName v, IRSourceLocation sloc)
		{ return new IRPointerIndexOp(OpCode.Op.LoadLocalAddr, v, null, null, 0, null, sloc); }

		/// <summary>
		/// Make the bytecode for loading the address of the given global variable.
		/// </summary>
		public static IRPointerIndexOp MakeLoadGlobalAddr(IRStaticField sfld, IRSourceLocation sloc)
		{ return new IRPointerIndexOp(OpCode.Op.LoadGlobalAddr, null, sfld, null, 0, null, sloc); }

		/// <summary>
		/// Make the bytecode indexing the pointer at stack[top].
		/// </summary>
		public static IRPointerIndexOp MakeFieldAddrIndex(IRMemberField fld, int soffset, IRSourceLocation srcloc)
		{ return new IRPointerIndexOp(OpCode.Op.IndexRefAddrByField, null, null, fld, soffset, null, srcloc); }

		/// <summary>
		/// Make the bytecode indexing the pointer to the array at stack[top-1] with an index on the stack.
		/// </summary>
		public static IRPointerIndexOp MakeArrayAddrIndex(IRArrayType arraytype, IRSourceLocation srcloc)
		{ return new IRPointerIndexOp(OpCode.Op.IndexRefAddrByArrayIndex, null, null, null, 0, arraytype, srcloc); }

		public override String ToString()
		{
			if(this.OperationCode == OpCode.Op.LoadLocalAddr)
				return "varaddr:    of " + this.m_var.ToString();
			else if(this.OperationCode == OpCode.Op.LoadGlobalAddr)
				return "staddr:     of " + this.m_sfld.ToString();
			else if(this.OperationCode == OpCode.Op.IndexRefAddrByField)
				return "indexptr:  fld " + "@ " + this.m_fieldindex.ToString();
			else if(this.OperationCode == OpCode.Op.IndexRefAddrByArrayIndex)
				return "indexptr: base " + "@ " + this.m_basearraytype.ToString();
			else
				return "--BAD OPCODE--";
		}

		/// <summary>
		/// If this is an load local address operation, this returns the var to get the address of.
		/// </summary>
		public IRVarName LocalName
		{
			get
			{
				Debug.Assert(this.OperationCode == OpCode.Op.LoadLocalAddr);
				return this.m_var;
			}
		}

		/// <summary>
		/// If this is an load global address operation, this returns the static field to get the address of.
		/// </summary>
		public IRStaticField StaticField
		{
			get
			{
				Debug.Assert(this.OperationCode == OpCode.Op.LoadGlobalAddr);
				return this.m_sfld;
			}
		}

		/// <summary>
		/// If this is an field index operation, this returns the member field.
		/// </summary>
		public IRMemberField IndexField
		{
			get
			{
				Debug.Assert(this.OperationCode == OpCode.Op.IndexRefAddrByField);
				return this.m_fieldindex;
			}
		}

		/// <summary>
		/// If this is an field index operation, this returns the position in the stack to load/store from.
		/// </summary>
		public int IndexStackOffset
		{
			get
			{
				Debug.Assert(this.OperationCode == OpCode.Op.IndexRefAddrByField);
				return this.m_stackposition;
			}
		}

		/// <summary>
		/// If this is an array index operation, this returns the type of the underlying array.
		/// </summary>
		public IRArrayType ArrayType
		{
			get
			{
				Debug.Assert(this.OperationCode == OpCode.Op.IndexRefAddrByArrayIndex);
				return this.m_basearraytype;
			}
		}
	}
}
